package com.example.demo.searcher;

import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

public class DocSearcher {

    //停用词文件的路径
    private static  String STOP_WORD_PATH=null;
    static {
        if(Config.isOnline){
            STOP_WORD_PATH="/root/java104/doc_searcher_index/stopword.txt";
        }else{
            STOP_WORD_PATH="D:\\doc_searcher_index\\stopword.txt";
        }
    }
    //使用hashset来保存停用词
    private HashSet<String> stopWords=new HashSet<>();
    private Index index=new Index();
    public DocSearcher(){
        index.load();
        loadStopWords();
    }
    //完成整个搜索过程的方法
    //参数(输入部分)就是用户给出的查询词
    //返回值(输出部分)就是搜索结果的集合
    public List<Result> search(String entry){
        //1.[分词] 针对entry这个查询词进行分词
        List<Term> oldTerms= ToAnalysis.parse(entry).getTerms();
        List<Term> terms=new ArrayList<>();
        //针对分词结果,使用暂停词表进行过滤
        for (Term term:oldTerms) {
            if(stopWords.contains(term.getName())){
                continue;
            }
            terms.add(term);

        }
        //2.[触发] 根据分词结果来查倒排
        //得到一个二维数组
        List<List<Weight>> termResult=new ArrayList<>();
        for (Term term:terms) {
            //获取到每一个分词的字面内容
            String word = term.getName();
            //虽然倒排索引中有很多的词,但是word不一定在这些词中
            List<Weight> invertedList=index.getInverted(word);
            if(invertedList==null){
                continue;
            }
            termResult.add(invertedList);
        }
        //3.对这个二维数组进行去重并权重合并
        List<Weight> allTermResult=mergeResult(termResult);

        //4.[排序] 根据触发结果按照权重排序
        allTermResult.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                return o2.getWeight()- o1.getWeight();
            }
        });
        List<Result> results=new ArrayList<>();
        //5.[包装结果] 针对排序的结果,去查正排,构造出要返回的数据.
        for(Weight weight:allTermResult){
            DocInfo docInfo= index.getDocInfo(weight.getDocId());
            Result result=new Result();
            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
            result.setDesc(GenDesc(docInfo.getContent(),terms));
            results.add(result);
        }

        return results;

    }

    static class Pos{
        public int row;
        public int col;

        public Pos(int row, int col) {
            this.row = row;
            this.col = col;
        }
    }
    private List<Weight> mergeResult(List<List<Weight>> source) {
        // 在进行合并的时候, 是把多个行合并成一行了.
        // 合并过程中势必是需要操作这个二维数组(二维List) 里面的每个元素的....
        // 操作元素就涉及到 "行" "列" 这样的概念~~ 要想确定二维数组中的一个元素, 就需要明确知道 行 和 列
        //1.先对每一行按照ID排序
        for(List<Weight> rowWeight:source){
            rowWeight.sort(new Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                     return o1.getDocId()- o2.getDocId();
                }
            });
        }

        //创建链表作为结果集
        ArrayList<Weight> resultList=new ArrayList<>();
        //2.创建一个优先队列用于获取source每一行的最小值
        PriorityQueue<Pos> priorityQueue=new PriorityQueue<>(new Comparator<Pos>() {
            @Override

            public int compare(Pos o1, Pos o2) {
                return source.get(o1.row).get(o1.col).getDocId()
                        -source.get(o2.row).get(o2.col).getDocId();
            }
        });
        //3.初始化优先队列,将每一行的第一个元素加入到队列中
        for (int i=0;i<source.size();i++) {
            priorityQueue.offer(new Pos(i,0));
        }

        //4.循环的取队首元素(也就是当前这若干行中最小的元素)
        while(!priorityQueue.isEmpty()){
            Pos curPos=priorityQueue.poll();
            Weight curWeight=source.get(curPos.row).get(curPos.col);
            if(!resultList.isEmpty()){
                Weight lastWeight=resultList.get(resultList.size()-1);
                //如果当前文章id和结果集中的文章相同,则进行权重合并
                if(lastWeight.getDocId()==curWeight.getDocId()){
                    resultList.get(resultList.size()-1).setWeight(lastWeight.getWeight()+ curWeight.getWeight());
                }else{
                    resultList.add(curWeight);
                }

            }else{
                resultList.add(curWeight);
            }
            //处理完该元素之后,就该行的下一个元素添加到队列中
            Pos newPos =new Pos(curPos.row, curPos.col+1);
            // 如果移动光标之后, 超出了这一行的列数, 就说明到达末尾了.
            // 到达末尾之后说明这一行就处理完毕了
            if(newPos.col>=source.get(newPos.row).size()){
                continue;
            }
            priorityQueue.offer(newPos);
        }
        return resultList;

    }

    private String GenDesc(String content, List<Term> terms) {
        //先遍历分词结果,看哪个分词结果在content中存在
        int firstPos=-1;
        for (Term term:terms) {
            String word = term.getName();
            content=content.toLowerCase().replaceAll("\\b"+word+"\\b"," "+word+" ");
            firstPos = content.toLowerCase().indexOf(" " + word + " ");
            if (firstPos >= 0) {
                //找到了位置
                break;
            }
        }
        if(firstPos==-1){
            //正文中没有该关键词
            if(content.length()>=160){
                return content.substring(0,160)+"...";
            }
            return content;
        }
        String desc="";
        int descBeg=firstPos>60?firstPos-60:0;
        if(descBeg+160>content.length()){
            desc=content.substring(descBeg);
        }else{
            desc=content.substring(descBeg,descBeg+160)+"...";
        }

        // 在此处加上一个替换操作. 把描述中的和分词结果相同的部分, 给加上一层 <i> 标签. 就可以通过 replace 的方式来实现.
        for (Term term : terms) {
            String word = term.getName();
            // 注意, 此处要进行全字匹配. 也就是当查询词为 List 的时候 不能把 ArrayList 中的 List 给单独标红
            desc = desc.replaceAll("(?i) " + word + " ", "<i> " + word + " </i>");
        }

        return desc;
    }



    public void loadStopWords(){
        try(BufferedReader bufferedReader=new BufferedReader(new FileReader(STOP_WORD_PATH))){
            while(true){
                String line = bufferedReader.readLine();
                if(line==null){
                    break;
                }
                stopWords.add(line);
            }
        }catch(IOException e){
            e.printStackTrace();

        }
    }
    public static void main(String[] args) {
        DocSearcher docSearcher=new DocSearcher();
        Scanner sc=new Scanner(System.in);
        while(true){
            System.out.println("请输入要查询的内容:");
            String entry=sc.next();
            List<Result> results=docSearcher.search(entry);
            if(results==null){
                System.out.println("抱歉,没有您要找的内容!");
            }
            for (Result result:results) {
                System.out.println("============================================================");
                System.out.println(result);

            }
        }
    }

}
